using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;

namespace Coriander.Jabber
{
    /// <summary>
    /// Minimal, lightweight Xmpp connection.
    /// </summary>
    /// <remarks>
    /// See: http://code.google.com/apis/talk/open_communications.html
    /// See: http://svn.ag-software.de/ags/trunk/agsxmpp/XmppClientConnection.cs
    /// 
    /// Connecting:
    /// 
    ///     1. Send an open stream request
    ///     2. Get the response back, this contains an element like:
    /// 
    ///         <stream:stream 
    ///             from="gmail.com" 
    ///             id="B50AB2E5B1C965B2" 
    ///             xmlns:stream="http://etherx.jabber.org/streams" 
    ///             xmlns="jabber:client"
    ///         >
    /// 
    ///     3. We have to wait for this to happen, block all other activity until
    ///        the response has been returned.
    /// </remarks>
    public class Connection
    {
        # region * Events *

        private event ReceivedEventHandler received;
        
        public delegate void ReceivedEventHandler(
            Object sender,
            ReceiveEventArgs e
        );

        /// <summary>
        /// 
        /// </summary>
        public event ReceivedEventHandler Received
        {
            add { received += value; }
            remove { received -= value; }
        }

        # endregion

        # region * Fields, properties and ctors *

        readonly Uri uri;
        readonly Socket socket = new Socket(
            AddressFamily.InterNetwork, 
            SocketType.Stream, 
            ProtocolType.IP
        ); 

        AsyncCallback writeCallback;
        AsyncCallback readCallback;

        Byte[] readBuffer = new Byte[1024];

        /// <summary>
        /// 
        /// </summary>
        public Uri Uri
        {
            get { return uri; }
        }

        /// <summary>
        /// ctor
        /// </summary>
        private Connection() : this(null) { }

        /// <summary>
        /// ctor : Uri
        /// </summary>
        /// <param name="uri"></param>
        public Connection(Uri uri)
        {
            this.uri = uri;
        }

        # endregion

        /// <summary>
        /// Opens a connection
        /// </summary>
        public void Open() 
        {
            socket.Connect(uri.Host, uri.Port);

            writeCallback   = new AsyncCallback(OnWriteCallback);
            readCallback    = new AsyncCallback(OnReadCallback);

            Write(GetStreamStartHeader("gmail.com"));
        }

        /// <summary>
        /// Writes the supplied command to the output stream
        /// </summary>
        /// <param name="command"></param>
        public void Write(String command) 
        {
            if (false == socket.Connected)
                throw new InvalidOperationException("Socket not connected"); 

            Console.WriteLine("SENDING: {0}", command);  

            Byte[] buffer;

            socket.BeginSend(
                buffer = Encoding.ASCII.GetBytes(command),
                0,
                buffer.Length,
                SocketFlags.None,
                writeCallback,
                null
            ).AsyncWaitHandle.WaitOne();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        IAsyncResult BeginRead() 
        {
            if (false == socket.Connected)
                throw new InvalidOperationException("Socket not connected"); 

            Array.Clear(readBuffer, 0, readBuffer.Length);

            return socket.BeginReceive(
                readBuffer,
                0,
                readBuffer.Length,
                SocketFlags.None,
                readCallback,
                null
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        void OnWriteCallback(IAsyncResult result)
        {
            Console.WriteLine(
                "SENT: {0} Bytes sent.", 
                socket.EndSend(result)
            );

            BeginRead();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        void OnReadCallback(IAsyncResult result)
        {
            Int32 bytesRead = socket.EndReceive(result);

            OnReceived(
                new ReceiveEventArgs(
                    Encoding.ASCII.GetString(readBuffer)
                )
            ); 

            if (bytesRead > 0)
            {
                BeginRead();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        void OnReceived(ReceiveEventArgs e) 
        {
            ReceivedEventHandler handler = received;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="host"></param>
        /// <returns></returns>
        static String GetStreamStartHeader(String host)
        {
            return GetStreamStartHeader(host, null, null);
        }

        /// <summary>
        /// Gets a start stream request
        /// </summary>
        /// <returns></returns>
        static String GetStreamStartHeader(
            String host, 
            Object version, 
            String language
        )
        {
            if (null == host)
                throw new ArgumentNullException("host");

            return Element.New().
            Append(         "<stream:stream").
            AppendFormat(   " to='{0}'", host).
            Append(         " xmlns='jabber:client'").
            Append(         " xmlns:stream='http://etherx.jabber.org/streams'").
            AppendFormatIf(version != null, 
                            " version='{0}'", version).
            AppendFormatIf(language != null, 
                            " xml:lang='{0}'", language).
            Append(">");
        }
    }

    /// <summary>
    /// Builds XMPP element nodes
    /// </summary>
    public class Element 
    {
        StringBuilder buffer = new StringBuilder();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="element"></param>
        /// <returns></returns>
        public Element AppendIf(Boolean condition,  String element) 
        {
            if (condition)
            {
                Append(element); 
            }

            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public Element Append(String element) 
        {
            return AppendFormat(element); 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Element AppendFormatIf(
            Boolean condition, 
            String format, 
            params Object[] args
        ) 
        {
            if (condition)
            {
                AppendFormat(format, args); 
            }

            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Element AppendFormat(String format, params Object[] args) 
        {
            buffer.AppendFormat(format, args);

            return this;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static implicit operator String(Element element)
        {
            return element.buffer.ToString();
        }

        public static Element New() { return new Element(); }
    }
}
